{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Create a Marquee Backcasted Portfolio with GS Quant\n",
    "\n",
    "The Marquee Portfolio Service provides a powerful framework for uploading portfolio positions and retrieving analytics including historical performance, factor risk exposure, ESG analytics, and more. GS Quant makes operating the suite of Portfolio Service API's intuitive and fast."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1: Authenticate and Initialize Your Session\n",
    "\n",
    "First you will import the necessary modules and add your client id and client secret."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import datetime as dt\n",
    "import warnings\n",
    "\n",
    "from gs_quant.datetime import business_day_offset\n",
    "from gs_quant.markets.portfolio import Portfolio\n",
    "from gs_quant.markets.portfolio_manager import PortfolioManager\n",
    "from gs_quant.markets.position_set import PositionSet\n",
    "from gs_quant.markets.report import FactorRiskReport, ThematicReport\n",
    "from gs_quant.models.risk_model import FactorRiskModel\n",
    "from gs_quant.session import GsSession, Environment\n",
    "from gs_quant.entities.entitlements import Entitlements, EntitlementBlock, User\n",
    "\n",
    "\n",
    "client = None\n",
    "secret = None\n",
    "\n",
    "## External users must fill in their client ID and secret below and comment out the line below\n",
    "\n",
    "# client = 'ENTER CLIENT ID'\n",
    "# secret = 'ENTER CLIENT SECRET'\n",
    "\n",
    "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n",
    "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n",
    "\n",
    "print('GS Session initialized.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Create the Portfolio\n",
    "\n",
    "The first step is to create a new, empty portfolio in Marquee."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio = Portfolio(name='My New Backcasted Portfolio')\n",
    "portfolio.save()\n",
    "\n",
    "print(f\"Created portfolio '{portfolio.name}' with ID: {portfolio.id}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once your portfolio has been saved to Marquee, the `PortfolioManager` class allows users to interact with their Marquee portfolios directly from GS Quant. We will be using `PortfolioManager` to update portfolio positions, entitlements, update custom AUM, and run reports."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pm = PortfolioManager(portfolio.id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Define Portfolio Entitlements (Optional)\n",
    "\n",
    "By default, an application will have all entitlement permissions to a portfolio it makes.\n",
    "However, if you would like to share the portfolio with others, either Marquee users or other\n",
    "applications, you will need to specify them in the entitlements parameter of the portfolio.\n",
    "Let's walk through how we convert a list of admin and viewer emails into an `Entitlements` object:\n",
    "\n",
    "*Note: If you would like to see this portfolio on your Marquee webpage, you'll need to add your account\n",
    "email address into the `portfolio_admin_emails` list*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio_admin_emails = ['LIST OF ADMIN EMAILS']\n",
    "portfolio_viewer_emails = ['LIST OF VIEWER EMAILS']\n",
    "\n",
    "admin_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_admin_emails))\n",
    "view_entitlements = EntitlementBlock(users=User.get_many(emails=portfolio_viewer_emails))\n",
    "\n",
    "entitlements = Entitlements(view=view_entitlements, admin=admin_entitlements)\n",
    "\n",
    "print(f'Entitlements:\\n{entitlements.to_dict()}')\n",
    "\n",
    "pm.set_entitlements(entitlements)\n",
    "\n",
    "print(f\"Updated entitlements for '{portfolio.name}'\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: Define Portfolio Positions\n",
    "\n",
    "Portfolio positions in Marquee are stored on a holding basis, when means you only upload positions for days where you are rebalancing your portfolio. Depending on your quantity type, take the following example positions:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### Uploading Positions Using Shares\n",
    "Portfolio quantity type uses share counts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "positions = [\n",
    "    {'identifier': 'AAPL UW', 'quantity': 25},\n",
    "    {'identifier': 'GS UN', 'quantity': 50},\n",
    "    {'identifier': 'META UW', 'quantity': 25},\n",
    "    {'identifier': 'AMZN UN', 'quantity': 50},\n",
    "    {'identifier': 'MSFT UW', 'quantity': 25},\n",
    "    {'identifier': 'AZN UW', 'quantity': 50},\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### Uploading Positions Using Exposures\n",
    "Portfolio quantity type uses notional values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "positions = [\n",
    "    {'identifier': 'AAPL UW', 'notional': 25000},\n",
    "    {'identifier': 'GS UN', 'notional': 50000},\n",
    "    {'identifier': 'META UW', 'notional': 25000},\n",
    "    {'identifier': 'AMZN UN', 'notional': 50000},\n",
    "    {'identifier': 'MSFT UW', 'notional': 25000},\n",
    "    {'identifier': 'AZN UW', 'notional': 50000},\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "For uploading using quantities or exposures, resolve the positions in your portfolio as of last business day."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "portfolio_position_sets = [\n",
    "    PositionSet.from_dicts(date=business_day_offset(dt.date.today(), -1, roll='forward'), positions=positions)\n",
    "]\n",
    "\n",
    "for pos_set in portfolio_position_sets:\n",
    "    pos_set.resolve()\n",
    "\n",
    "print(\"All positions have been resolved and saved.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### Uploading Positions Using Weights\n",
    "Portfolio quantity type uses weight values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "positions = [\n",
    "    {'identifier': 'AAPL UW', 'weight': 0.25},\n",
    "    {'identifier': 'GS UN', 'weight': 0.25},\n",
    "    {'identifier': 'META UW', 'weight': 0.25},\n",
    "    {'identifier': 'AMZN UN', 'weight': 0.25},\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For uploading using weights, resolve the positions in your portfolio as of last business day and using a desired target notional."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "portfolio_position_sets = [\n",
    "    PositionSet.from_dicts(\n",
    "        date=business_day_offset(dt.date.today(), -1, roll='forward'), reference_notional=15000, positions=positions\n",
    "    )\n",
    "]\n",
    "\n",
    "for pos_set in portfolio_position_sets:\n",
    "    pos_set.resolve()\n",
    "\n",
    "print(\"All positions have been resolved and saved.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Price Positions (Weights & Exposures)\n",
    "When uploading positions by weights or exposures they must priced and converted to share quantities before being uploaded to the portfolio.\n",
    "Select the currency you want to price your positions with. If you do not specify, default is USD."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "currency = 'USD'\n",
    "\n",
    "for pos_set in portfolio_position_sets:\n",
    "    pos_set.price(currency)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Define Currency for portfolio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from gs_quant.common import Currency\n",
    "\n",
    "pm = PortfolioManager(portfolio.id)\n",
    "pm.set_currency(Currency.USD)  # Set the currency using PortfolioManager"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "## Step 5: Upload the positions onto the portfolio:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pm.update_positions(portfolio_position_sets)\n",
    "\n",
    "print(f\"Successfully updated positions for portfolio {pm.portfolio_id}\\n\")\n",
    "print(f\"Latest updated set of positions for {pm.portfolio_id}: \\n{pm.get_latest_position_set().get_positions()}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 6: Create Factor Risk and/or Thematic Reports (Optional)\n",
    "\n",
    "By default, creating a portfolio will automatically create a corresponding performance report for it as well.\n",
    "If you would like to create a factor risk and/or thematic report (more documentation on reports found [here](https://developer.gs.com/p/docs/services/portfolio/programmatic-access/reports/))\n",
    "for it as well, run the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "risk_model_id = 'RISK MODEL ID'\n",
    "\n",
    "# Add a factor risk report\n",
    "risk_report = FactorRiskReport(risk_model_id=risk_model_id)\n",
    "risk_report.set_position_source(portfolio.id)\n",
    "risk_report.save()\n",
    "\n",
    "# Add a thematic report\n",
    "thematic_report = ThematicReport()\n",
    "thematic_report.set_position_source(portfolio.id)\n",
    "thematic_report.save()\n",
    "\n",
    "print('All portfolio reports created.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### Quick Tip!\n",
    "\n",
    "*Explore the different factor risk models available in Marquee in our [Data Catalog](https://marquee.gs.com/s/discover/data-services/catalog?Category=Factor+Risk+Model)*\n",
    "\n",
    "*Premium clients get access to many more risk models (including premium vendors like MSCI Barra),\n",
    "while non-premium clients get access to a limited suite of models. To see which models you have access to,\n",
    "simply run the following:*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "risk_models = FactorRiskModel.get_many(limit=100)\n",
    "for risk_model in risk_models:\n",
    "    print(f'{risk_model.name}: {risk_model.id}\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 7: Schedule Reports\n",
    "\n",
    "Now, it's schedule all the portfolio reports. Once this is done and reports are completed, you can programmatically retrieve factor risk and attribution data for your portfolio.\n",
    "\n",
    "When scheduling reports, you have two options:\n",
    "- Backcast the report: Take the earliest date with positions in the portfolio / basket and run the report on the positions held then with a start date before the earliest position date and an end date\n",
    " of the earliest position date. This option is ideal for snapshot portfolios.\n",
    "- Do not backcast the report: Set the start date as a date that has positions in the portfolio or basket and an end date after that (best practice is to set it to T-1). In this case the\n",
    " report will run on positions held as of each day in the date range. This option is ideal for historical portfolios."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "pm.schedule_reports(backcast=True)\n",
    "\n",
    "print('All portfolio reports scheduled.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: Cannot Schedule backcasted report with no position sets on the report job end date."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
